package com.jt;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.atomic.AtomicLong;

//@EnableFeignClients注解描述启动类时,用于告诉springboot在启动时,
//扫描启动类所在包或子包中的类,假如接口上有@FeignClient注解描述,则对
//这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
    /**创建RestTemplate对象,然后基于此对象进行远程服务调用
     * @Bean 注解用于描述方法,用于告诉spring框架这个方法的返回值交给spring管理,
     * spring默认会这个方法返回对象起一个bean的名字(key),默认为方法名.当然也可以
     * 对一个bean的名字进行自定义,例如@Bean("自己定义的名字").
     * 思考:spring框架中为什么会给出这样的注解,用于描述方法,在方法中构建对象?
     * 第一:Spring是一个资源整合框架.
     * 第二:何为资源?(内存中对象)
     * 第三:所有对象是否都有类型?
     * 第四:所有对象的类型都是你自己定义的吗?不一定(有自己,有第三方)
     * 第五:第三方的类,我们能在类上直接添加注解描述吗(例如@Component)?不可以
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    @Bean
    @LoadBalanced //这个注解描述RestTemplate对象时,系统底层会对RestTemplate对象的请求进行拦截
    public RestTemplate loadBalanceRestTemplate(){
        return new RestTemplate();
    }
    @Service
    public class ConsumerService{
        @SentinelResource("doGetResource")
        public String doGetResource(){
            return "doGetResource";
        }
        //....
    }
    @RestController
    public class ConsumerController{
        @Autowired
        private RestTemplate restTemplate;
        /**
         * @Autowired注解描述属性时,会告诉spring框架,要优先按属性类型进行对象的查找和注入,假如
         * 此类型的对象存在多个,此时还会按照属性名进行查找和比对,有相同的则直接注入(DI),没有相同
         * 的则出错,当然也可以在属性上添加@Qualifier("bean的名字")注解,指定要注入的具体对象
         */
        @Autowired
        private RestTemplate loadBalanceRestTemplate;
        //负载均衡客户端对象(基于此对象可以从nacos中获取服务列表,并且可以基于一定的算法
        //从列表中获取一个服务实例)
        @Autowired
        private LoadBalancerClient loadBalancerClient;

        @Value("${spring.application.name}")
        private String appName;

        @Autowired
        private ConsumerService consumerService;


        //http://localhost:8090/consumer/findById?id=10
        @GetMapping("/consumer/findById")
        @SentinelResource("res")
        public String doFindById(@RequestParam Integer id){
            return "select by "+id;
        }


        //J.U.C (原子操作对象,提供了线程安全的操作)
        private AtomicLong atomicLong=new AtomicLong(1);
        //直接调用(非负载均衡方式)
        //http://localhost:8090/consumer/doRestEcho1
        @GetMapping("/consumer/doRestEcho1")
        public  String doRestEcho1() throws InterruptedException {
            long num=atomicLong.getAndIncrement();
            if(num%2==0) {//通过这个操作模拟慢调用
                Thread.sleep(300);
            }
            //String tName=Thread.currentThread().getName();
            //System.out.println("thread.name="+tName);
            //consumerService.doGetResource();
            String url="http://localhost:8081/provider/echo/"+appName;
            return restTemplate.getForObject(url,String.class);
        }

        //负载均衡方式的调用1
        @GetMapping("/consumer/doRestEcho2")
        public String doRestEcho2(){
            consumerService.doGetResource();
            //基于loadBalancerClient方式获取服务实例
            String serviceId="sca-provider";//这个名字要在nacos的服务列表中
            ServiceInstance choose =
                    loadBalancerClient.choose(serviceId);
            String ip=choose.getHost();
            int port=choose.getPort();
            //String url="http://"+ip+":"+port+"/provider/echo/"+appName;
            String url=String.format("http://%s:%s/provider/echo/%s",ip,port,appName);
            return restTemplate.getForObject(url,String.class);
        }
        //负载均衡应用方式2:@LoadBalance
        @GetMapping("/consumer/doRestEcho3")
        public String doRestEcho3(){
            String serviceId="sca-provider";
            String url=String.format("http://%s/provider/echo/%s",serviceId,appName);
            //底层在基于loadBalanceRestTemplate对象访问方式,会启动一个拦截器
            return loadBalanceRestTemplate.getForObject(url,String.class);
        }
    }
}





